/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package pgentity.events.release_event;

import config.CFPrize;
import config.CFReleaseEvent;
import config.PGConfig;
import db.DBContext;
import db.PGKeys;
import java.util.*;
import logging.Logging;
import logging.report.ActionRecord;
import logging.report.UserAction;
import pgentity.Cote;
import pgentity.CoteList;
import pgentity.EggStore;
import pgentity.EntityContext;
import pgentity.Gift;
import pgentity.Penguin;
import pgentity.PenguinList;
import pgentity.User;
import pgentity.prize.IPrizeFactory;
import pgentity.prize.PrizeFactory;
import pgentity.quest.QuestChecker;
import pgentity.quest.QuestCheckerFactory;
import pgentity.quest.QuestFactory;
import pgentity.quest.QuestLogger;
import pgentity.services.EggStoreServices;
import pgentity.services.GiftServices;
import pgentity.services.PenguinServices;
import pgentity.services.PenguinUpdator;
import pgentity.services.QuestServices;
import pgentity.services.UserServices;
import share.AMFBuilder;
import share.PGError;
import share.PGException;
import share.PGHelper;
import share.PGMacro;

/**
 *
 * @author Salm
 */
public class ReleaseEventServices {
    private ReleaseEventServices() {}
    
    public static final ReleaseEventServices SERVICES = new ReleaseEventServices();
    
    public static void init()
    {
        Keys.init();
    }
    
    private Collection<PenguinUpdator> getPenguinUpdator(RECote eventCote,
            Collection<Penguin> penguins, long now)
    {
        List<PenguinUpdator> ret = new ArrayList(penguins.size());
        for (Penguin penguin : penguins) {
            REPenguin eventPenguin = REPenguin.getPenguin(penguin);
            ret.add(new REPenguinUpdator(eventCote, eventPenguin, now));
        }
        
        return ret;
    }
    
    public Collection<PenguinUpdator> getPenguinUpdator(Cote cote,
            Collection<Penguin> penguins, long now)
    {
        RECote eventCote = RECote.getCote(cote);
        return getPenguinUpdator(eventCote, penguins, now);
    }
    
    public Object spawnReleaseEventItems(Cote cote,
            Collection<Penguin> penguins, long now) throws PGException
    {
        for (Penguin penguin : penguins) {
            long nextEat = PenguinServices.inst()
                    .nextEatWithoutPoolfish(penguin, cote);
            PGException.Assert(nextEat > now,
                    PGError.PENGUIN_IS_HUNGRIED, "Penguin is hungried");
        }
        
        final RECote eCote = RECote.getCote(cote);
        Collection<REPenguin> ePenguins = new HashSet(penguins.size());
        for (Penguin penguin : penguins) {
            ePenguins.add(REPenguin.getPenguin(penguin));
        }
        
        PriorityQueue<REPenguinUpdator> penguinUpdators = new PriorityQueue(penguins.size(),
            new Comparator<REPenguinUpdator>() {
            @Override
            public int compare(REPenguinUpdator p1, REPenguinUpdator p2) {
                long p1NextSpawnTime = p1.nextActionTime();
                long p2NextSpawnTime = p2.nextActionTime();
                
                return (p1NextSpawnTime > p2NextSpawnTime)?1:
                        ((p1NextSpawnTime == p2NextSpawnTime)?0:-1);
            }
        });
        
        Map<String, Object> failData = new HashMap();
        
        // init penguin entities
        for (REPenguin penguin : ePenguins) {
            REPenguinUpdator updator = new REPenguinUpdator(eCote, penguin, now);
            long nextSpawn = updator.nextActionTime();
            if (nextSpawn > now)
            {
                Map<String, Object> failPenguinData = new HashMap(2);
                failPenguinData.put(PGMacro.TIME_LAST_SPAWN, penguin.getLastSpawnTime());
                
                failData.put(penguin.getPenguin().getPenguinID(), failPenguinData);
            }
            else
            {
                penguinUpdators.add(updator);
            }
        }
        
        Map<String, Object> successData = new HashMap(ePenguins.size());
        
        while (!penguinUpdators.isEmpty())
        {
            REPenguinUpdator updator = penguinUpdators.poll();
            updator.update();
            
            successData.put(updator.getPenguinID(), updator.getLastSpawnedItem());
            updator.save();
        }
        
        Map<String, Object> response = new HashMap();
        response.put(PGMacro.SUCCESS, successData);
        response.put(PGMacro.FAIL, failData);
        return response;
    }
    
    public void resetSpawnTime(Penguin penguin, long time)
    {
        REPenguin rePenguin = REPenguin.getPenguin(penguin);
        rePenguin.setLastSpawnTime(time);
        rePenguin.saveToDB();
    }
    
    public Object pickReleaseEventItems(User user, Cote cote,
            Map<String, Number> items)
    {
        RECote eCote = RECote.getCote(cote);
        Map<String, Number> validItems = EggStoreServices.inst()
                .validateEgg(eCote.Items, items);
        
        EggStore eInv = REUser.getUser(user).inv();
        
        EggStoreServices.inst().moveEgg(eCote.Items, eInv, validItems);
        
        Map<String, Object> resp = new HashMap(2);
        
        resp.put(PGMacro.SUCCESS_EGGS, validItems);
        
        // build failed eggs
        if (!validItems.equals(items))
        {
            Map<String, Number> failedEggs = EggStoreServices.inst()
                    .substractEggs(items, validItems);
            
            resp.put(PGMacro.FAILED_EGGS, failedEggs);
        }
        
        return resp;
    }
    
    public Object returnReleaseEvent(EntityContext context, String eventID, long now)
    {
        CFReleaseEvent.Event conf = PGConfig.inst().releaseEvent().getEvents().get(eventID);
        PGException.Assert(conf != null, PGError.INVALID_CONFIG, "Invalid event id");
        
        UserEvents uEvents = UserEvents.getEvents(context.getUid());
        int nRecevied = PGHelper.toInt(uEvents.getData(eventID));
        PGException.Assert(nRecevied < conf.getReceiveable(), PGError.RETURNED_EVENT,
                "You have received max this event");
        
        QuestChecker eventChecker = QuestFactory.getChecker(
                uEvents.getCheckerLogPool(), conf.getActions());
        
        PGException.Assert(eventChecker.isAccept(context),
                PGError.INCOMPLETED_EVENT,
                "Not have enough resource for complete this event");
        
        eventChecker.returnQuest(context);
        uEvents.incData(eventID, 1);
        
        //prizing
        Map<Integer, CFPrize> gifts = conf.getGifts();
        Map<Integer, Object> giftAMFs = new HashMap(gifts.size());
        Collection<String> uids = Arrays.asList(new String[]{context.getUid()});
        for (Map.Entry<Integer, CFPrize> giftEntry : gifts.entrySet()) {
            Integer index = giftEntry.getKey();
            CFPrize prize = giftEntry.getValue();
            
            Gift gift = GiftServices.inst().sendGift(
                uids,
                prize.get(),
                now,
                PGConfig.inst().temp().RandomizePrize_Expire);
            giftAMFs.put(index, gift.buildIDAMF());
        }
        
        // log
        Logging.log(new ActionRecord(context.getUser(), now,
                UserAction.TRA_EVENT_RE, eventID));
        
        return AMFBuilder.make(PGMacro.GIFTS, giftAMFs);
    }
    
    public Map<Integer, Gift> receiveTotalEvent(EntityContext context, long now)
    {
        // check complete event
        UserEvents uEvents = UserEvents.getEvents(context.getUid());
        boolean receivedTotal = PGHelper.toInt(uEvents.getData("finished")) > 0;
        PGException.Assert(!receivedTotal,
                PGError.RETURNED_EVENT, "Have received total event");
        
        CFReleaseEvent conf = PGConfig.inst().releaseEvent();
        Set<String> allEvents = conf.getEvents().keySet();
        for (String eventID : allEvents) {
            int nReceived = PGHelper.toInt(uEvents.getData(eventID));
            PGException.Assert(
                    nReceived >= conf.getEvents().get(eventID).getNeed(),
                    PGError.INCOMPLETED_EVENT,
                    "Haven't complete other events");
        }
        
        uEvents.incData("finished", 1);
        Collection<String> uids = Arrays.asList(new String[] {context.getUid()});
        Map<Integer, CFPrize> prizes = PGConfig.inst().releaseEvent().getTotalPrize();
        Map<Integer, Gift> gifts = new HashMap(prizes.size());
        
        for (Map.Entry<Integer, CFPrize> prizeData : prizes.entrySet()) {
            Integer index = prizeData.getKey();
            CFPrize pz = prizeData.getValue();
            
            Gift gift = GiftServices.inst().sendGift(uids, pz.get(), now,
                    PGConfig.inst().temp().RandomizePrize_Expire);
            
            gifts.put(index, gift);
        }
        
        // log
        Logging.log(new ActionRecord(context.getUser(), now,
                UserAction.TRA_EVENT_TONG_RE));
        
        return gifts;
    }
    
    public void buyREItem(User user, String itemID, String paymentType, int nItem, long now)
    {
        CFReleaseEvent.Item item = PGConfig.inst().releaseEvent().getItems().get(itemID);
        PGException.Assert(item != null, PGError.INVALID_REITEM,
                "Invalid item " + itemID);
        
        int totalPrice = 0;
        if (PGMacro.GOLD.equals(paymentType))
        {
            totalPrice = item.getGoldPrice() * nItem;
            PGException.Assert(user.getGold() >= totalPrice,
                    PGError.NOT_ENOUGH_COIN, "User has not enough gold");
            
            QuestLogger qLogger = QuestServices.inst().getQuestLogger(user.getUid(), now);
            UserServices.inst().decreaseGold(user, qLogger, totalPrice,
                    now, UserAction.MUA_ITEM_RE);
        }
        else if (PGMacro.COIN.equals(paymentType))
        {
            totalPrice = item.getCoinPrice() * nItem;
            PGException.Assert(user.getCoin() >= totalPrice,
                    PGError.NOT_ENOUGH_COIN, "User has not enough coin");
            
            UserServices.inst().decreaseCoin(user, totalPrice,
                    now, UserAction.MUA_ITEM_RE);
        }
        else
        {
            PGException.Assert(false, PGError.INVALID_REITEM,
                    "Invalid payment");
        }
        
        // log
        Logging.log(new ActionRecord(user, now, UserAction.MUA_ITEM_RE, itemID,
                paymentType, totalPrice));
        
        REUser eUser = REUser.getUser(user);
        eUser.inv().addEggs(itemID, nItem);
    }
    
    public void addItemToCote(Cote cote, String itemID)
    {
        CFReleaseEvent reConfig = PGConfig.inst().releaseEvent();
        if (reConfig.getItems().containsKey(itemID))
        {
            RECote reCote = RECote.getCote(cote);
            reCote.Items.addEgg(itemID);
        }
    }
    
    public Object buildAMF(String uid)
    {
        Map<String, Object> resp = new HashMap(3);
        resp.put(PGMacro.USER, REUser.getUser(User.getUser(uid)).buildAMF());
        
        List<String> coteIDs = CoteList.getCotes(uid).getAll();
        Map<String, Object> eCotesAMF = new HashMap(coteIDs.size());
        for (String coteID : coteIDs) {
            eCotesAMF.put(coteID, buildCoteAMF(uid, coteID));
        }
        
        resp.put(PGMacro.COTE, eCotesAMF);
        
        UserEvents uEvents = UserEvents.getEvents(uid);
        resp.put(PGMacro.RELEASE_EVENT, uEvents.dump());
        
        return resp;
    }
    
    private Object buildCoteAMF(String uid, String coteID)
    {
        Map<String, Object> coteAMF = new HashMap(2);
        
        coteAMF.put(PGMacro.DATA,
                RECote.getCote(Cote.getCote(uid, coteID)).buildAMF());
        
        Collection<Penguin> penguins = Penguin.getPenguins(uid, coteID,
                PenguinList.getPenguinList(uid, coteID).getAll());
        Map<String, Object> penguinAMFs = new HashMap(penguins.size());
        
        for (Penguin p : penguins) {
            REPenguin penguin = REPenguin.getPenguin(p);
            penguinAMFs.put(p.getPenguinID(), penguin.buildAMF());
        }
        
        coteAMF.put(PGMacro.PENGUIN_LIST, penguinAMFs);
        
        return coteAMF;
    }
    
    public void registerCheckers(Map<String, QuestCheckerFactory> pool)
    {
        pool.put("re_items", REItemsChecker.Factory.inst());
    }
    
    public void registerPrizes(Map<String, IPrizeFactory> pool)
    {
        pool.put("re_items", REItemsPrize.Factory.inst());
    }
    
    public Object dump(String uid)
    {
        Map<String, Object> data = new HashMap();
        
        data.put(PGMacro.USER, REUser.getUser(uid).dump());
        data.put(PGMacro.RELEASE_EVENT, UserEvents.getEvents(uid).dump());
        
        CoteList coteList = CoteList.getCotes(uid);
        Collection<String> coteIDs = coteList.getAll();
        List<Object> coteData = new ArrayList(coteIDs.size());
        for (String coteID : coteIDs) {
            coteData.add(dumpCote(uid, coteID));
        }
        data.put(PGMacro.COTE, coteData);
        
        String token = PGConfig.inst().releaseEvent().token();
        return AMFBuilder.make(token, data);
    }
    
    private Object dumpCote(String uid, String coteID)
    {
        Map<String, Object> data = new HashMap();
        data.put(PGMacro.COTE, RECote.getCote(Cote.getCote(uid, coteID)).dump());
        
        return data;
    }
    
    public void restore(String uid, Map<String, Object> data, long now)
    {
        if (PGHelper.isNullOrEmpty(data))
            return;
        
        String token = PGConfig.inst().releaseEvent().token();
        data = (Map) data.get(token);
        if (PGHelper.isNullOrEmpty(data))
            return;
        
        destroy(uid);
        Map<String, Object> userData = (Map) data.get(PGMacro.USER);
        Map<String, Object> inv = (Map) userData.get(PGMacro.EGGS);
        DBContext.Redis().hset(REUser.key(uid).getChild(PGKeys.FD_EGGS), (Map) inv);
        
        restoreEvents(uid, data.get(PGMacro.RELEASE_EVENT));
        
        CoteList coteList = CoteList.getCotes(uid);
        List<String> coteIDs = coteList.getAll();
        List<Object> coteData = (List) data.get(PGMacro.COTE);
        
        int n = Math.min(coteIDs.size(), coteData.size());
        for (int i = 0; i < n; ++i)
        {
            restoreCote(uid, coteIDs.get(i), (Map) coteData.get(i), now);
        }
    }
    
    private void restoreEvents(String uid, Object data)
    {
        if (data == null || !(data instanceof Map))
            return;
        
        Map<String, String> mData = (Map) data;
        DBContext.Redis().hset(UserEvents.key(uid), mData);
    }
    
    private void restoreCote(String uid, String coteID, Map<String, Object> data, long now)
    {
        Map<String, Object> coteData = (Map) data.get(PGMacro.COTE);
        Map<String, String> items = (Map) coteData.get(PGMacro.EGGS);
        DBContext.Redis().hset(RECote.key(uid, coteID).getChild(PGKeys.FD_EGGS), items);
        
        PenguinList penguinIDs = PenguinList.getPenguinList(uid, coteID);
        Collection<String> pIDs = penguinIDs.getAll();
        Collection<Penguin> penguins = Penguin.getPenguins(uid, coteID, pIDs);
        
        for (Penguin penguin : penguins) {
            REPenguin p = REPenguin.getPenguin(penguin);
            p.setLastSpawnTime(now);
            p.saveToDB();
        }
    }
    
    public void destroy(String uid)
    {
        REUser.destroy(uid);
        UserEvents.destroy(uid);
        
        CoteList coteList = CoteList.getCotes(uid);
        Collection<String> coteIDs = coteList.getAll();
        for (String coteID : coteIDs) {
            destroyCote(uid, coteID);
        }
    }
    
    private void destroyCote(String uid, String coteID)
    {
        RECote.destroy(uid, coteID);
        PenguinList penguinIDs = PenguinList.getPenguinList(uid, coteID);
        Collection<String> pIDs = penguinIDs.getAll();
        
        for (String pID : pIDs) {
            REPenguin.destroy(uid, coteID, pID);
        }
    }
}
